use anyhow::Result;
use axum::extract::State;
use axum::routing::{get, patch};
use axum::Json;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::time::sleep;
use tracing::{info, instrument, span};
use tracing_core::LevelFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::fmt::Layer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

#[tokio::main]
async fn main() -> Result<()> {
    let console = Layer::new()
        .with_ansi(true)
        .with_line_number(true)
        .with_target(true)
        .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
        .with_thread_ids(true) // include the thread ID of the current thread
        .with_thread_names(true) // include the name of the current thread
        .json();
    tracing_subscriber::registry()
        .with(LevelFilter::INFO)
        .with(console)
        .init();

    let user = User {
        name: "abc".to_string(),
        age: 10,
        skills: vec!["Rust".to_string(), "Python".to_string()],
    };

    let user_arc = Arc::new(Mutex::new(user));
    let address = "127.0.0.1:8080";
    let app = axum::Router::new()
        .route("/", get(user_handler))
        .route("/", patch(update_handler))
        .with_state(user_arc);
    let listener = tokio::net::TcpListener::bind(address).await?;
    info!("starting server on {}", address);
    axum::serve(listener, app.into_make_service()).await?;
    Ok(())
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
struct User {
    name: String,
    age: u8,
    skills: Vec<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct UserUpdate {
    age: Option<u8>,
    skills: Option<Vec<String>>,
}

#[instrument]
async fn user_handler(State(user): State<Arc<Mutex<User>>>) -> Json<User> {
    sleep(Duration::from_millis(111)).await;
    span!(tracing_core::metadata::Level::INFO, "my span");
    info!(http.status_code = 200, "my span request");
    user.lock().unwrap().clone().into()
}

#[instrument]
async fn update_handler(
    State(user): State<Arc<Mutex<User>>>,
    Json(user_update): Json<UserUpdate>,
) -> Json<User> {
    let mut user = user.lock().unwrap();

    if let Some(age) = user_update.age {
        user.age = age;
    }
    if let Some(skills) = user_update.skills {
        user.skills = skills;
    }

    (*user).clone().into()
}
